'use strict';

const fs = require('fs-extra'),
  URL = require('url'),
  http = require('http');

const download = (url, dest, options = {}) => {
  const parsedUrl = URL.parse(url);

  return new Promise((res, rej) => {
    let pending = true;

    const resolve = (r) => ((pending = false), res(r)),
      reject = (e) => ((pending = false), rej(e));

    fs.stat(dest, (err, stat) => {
      if (err && err.code !== 'ENOENT') return reject(err);

      let start = stat ? stat.size : 0;

      const req = http.request(
        Object.assign(
          {
            method: options.method || 'GET',
            headers: Object.assign({ Range: 'bytes=' + start + '-' }, options.headers),
            timeout: 10000,
          },
          parsedUrl
        ),
        (res) => {
          options.onresponse && options.onresponse(res);

          if (res.statusCode === 416) return fs.unlink(dest, () => resolve(pending && download(url, dest, options)));

          if (res.statusCode >= 400) return reject(new Error('InvalidStatusCode:' + res.statusCode));

          if (res.headers.location) return resolve(pending && download(res.headers.location, dest, options));

          const file = fs.createWriteStream(dest, {
            flags: start ? 'r+' : 'w',
            start,
          });

          file.on('error', (e) => {
            reject(e);
            req.abort();
          });

          const length = res.headers['content-length'] ? parseInt(res.headers['content-length'], 10) : 0;

          let downloaded = start;

          emitProgress();
          res.on('data', (chunk) => {
            downloaded += chunk.length;
            emitProgress();
            file.write(chunk);
          });

          res.on('end', () => file.end());
          file.on('finish', () => {
            if (!res.complete && pending) return resolve(download(url, dest, options));

            emitProgress();
            resolve(dest);
          });

          function emitProgress() {
            typeof options.onprogress === 'function' && options.onprogress(downloaded, length);
          }
        }
      );

      options.onrequest && options.onrequest(req);

      req.on('error', reject);
      req.on('timeout', () => (req.abort(), reject(new Error('Timeout'))));
      req.on('abort', () => reject(new Error('Aborted')));
      req.end();
    });
  });
};

module.exports = download;
